<?php
/**
 * Celebrio FileSystem module
 *
 * @copyright  Copyright (c) 2011 Celebrio Software
 * @package    Celebrio
 * @subpackage FileSystem
 */
namespace Celebrio\FileSystem;

use Nette\Object;

use dibi;
use InvalidArgumentException;

use Celebrio\FileSystem\IFile;
use Celebrio\FileSystem\VirtualFileSystem;

/**
 * Description of VirtualFile
 *
 * @author pavel, Honza
 */
class VirtualFile extends GenericFile implements IFile {
    
    const VIRTUAL_ROOT = "root";
    
    /** @var int PK */
    private $id = null;
    
    /** @var int FK to users*/
    private $ownerId = null;
    
    /** @var bool */
    private $hidden = false;
    
    /** @var bool */
    private $protected = false;
    
    /** @var bool */
    private $encrypted = false;
    
    /** @var int FK to users*/
    private $lockedById = null;
    
    /** @var bool */
    private $deleted = false;
    
    /** @var int FK to itself */
    private $parent = null;
    
    /** @var string(128) */
    private $hash = null;

    /** @var int FK to mimetypes */
    private $mimetypeId = null;
    
    /** @var int FK to files_storages */
    private $fstorageId = null;
    

    /**
     * Checks whether the URL is valid and sets it. The URL
     * must start with 'root/' prefix (virtualized root directory).
     *
     * @throws Exception when the name or url is invalid
     * @todo change the exception type and catch it
     */
    public function  setUrl($url) {
        if ($this->isRoot($url, $this->name, false)) {
            $this->url = null;
        } else {
            parent::setUrl($url);
            if ((substr($this->getUrl(), 0, strlen(VirtualFile::VIRTUAL_ROOT)+1)) != VirtualFile::VIRTUAL_ROOT . "/") {
                throw new InvalidArgumentException("URL must start with virtual " . VirtualFile::VIRTUAL_ROOT . "/ directory prefix.");
            }
        }
    }

    public function getId() {
        return $this->id;
    }

    public function setId($id) {
        $this->id = $id;
    }

    public function getOwnerId() {
        return $this->ownerId;
    }

    public function setOwnerId($ownerId) {
        $this->ownerId = $ownerId;
    }

    public function getHidden() {
        return $this->hidden;
    }

    public function setHidden($hidden) {
        $this->hidden = $hidden;
    }

    public function getProtected() {
        return $this->protected;
    }

    public function setProtected($protected) {
        $this->protected = $protected;
    }

    public function getEncrypted() {
        return $this->encrypted;
    }

    public function setEncrypted($encrypted) {
        $this->encrypted = $encrypted;
    }

    public function getLockedById() {
        return $this->lockedById;
    }

    public function setLockedById($lockedById) {
        $this->lockedById = $lockedById;
    }

    public function getDeleted() {
        return $this->deleted;
    }

    public function setDeleted($deleted) {
        $this->deleted = $deleted;
    }

    public function getParent() {
        return $this->parent;
    }

    public function setParent($parent) {
        $this->parent = $parent;
    }

    public function getHash() {
        return $this->hash;
    }

    public function setHash($hash) {
        $this->hash = $hash;
    }

    public function getMimetypeId() {
        return $this->mimetypeId;
    }

    public function setMimetypeId($mimetypeId) {
        $this->mimetypeId = $mimetypeId;
    }
    
    public function getFstorageId() {
        return $this->fstorageId;
    }

    public function setFstorageId($fstorageId) {
        $this->fstorageId = $fstorageId;
    }


    /**
     * From the name (foo.txt) and url (root/testdir/testsubdir) creates
     * the array of items (file and directories) with their paths.
     *
     * It looks just like this:
     * result = array(
     *      0 => array(
     *              "url"  => "root/testdir/testsubdir/", 
     *              "name" => "foo.txt",
     *          ),
     *      1 => array(
     *              "url" => "root/testdir/",
     *              "name" => "testsubdir",
     *          ),
     *      2 => array(
     *              "url" => "root/",
     *              "name" => "testdir",
     *          ),
     *      3 => array(
     *              "url" => NULL,
     *              "name" => "root",
     *          ),
     *  )
     *
     * @return array(int => array("url" => string, "name" => string)), 
     *      look above for details
     */
    public function pathAsArray() {
        $path = explode("/", $this->getUrl() . $this->getName());

        $pathWithUrls = array();
        $url = null;

        $result = array();
        foreach ($path as $entry) {
            $result[] = array(
                    "url" => $url,
                    "name" => $entry,
                );
            $url = $url . $entry . "/" ;
        }

        return array_reverse($result);
    }
    
    /**
     * Determines whether the current VirtualFile object is root. When the
     * arguments are specified, the real object attributes are replaced
     * by the given parameters. So the method calculates if "the object would
     * be root with those parameters". The last parameter tells whether
     * empty string is allowed instead of null in root url attribute.
     * 
     * @param String $url (implicit FALSE because null is possible input value)
     * @param String $name (implicit FALSE because null is possible (incorrect) input value)
     * @param boolean $emptyStringRootUrlAllowed
     * @return boolean TRUE when the object is root, FALSE otherwise
     */
    public function isRoot($url = false, $name = false, $emptyStringRootUrlAllowed = false) {
        if ($url === false) {
            $url = $this->getUrl();
        }
        if ($name === false) {
            $name = $this->getName();
        }
        
        if ($name === VirtualFile::VIRTUAL_ROOT 
                && ($url === null || ($url === "" && $emptyStringRootUrlAllowed))) {
            return true;
        } else {
            return false;
        }
    }
    
    public function isDirectory() {
        $dirMimetypeId = 1; // this might be handled in a better way, searching DB and caching
        return $this->getMimetypeId() === $dirMimetypeId;
    }
    
/* ---------------------------------------------------------------------------*/
/* ------------------- STATIC CREATE-FROM-DATA METHODS -----------------------*/
/* ---------------------------------------------------------------------------*/
    
    /**
     * Creates new instance of VirtualFile from given DataFile. URL, name and 
     * mimetype attributes are converted to the new VirtualFile object.
     * 
     * The second parameter determines, which parts of DataFile will be
     * converted to the VirtualFile. All kinds of information which are
     * present in the array as the key (for example "fstorage") with
     * TRUE value will be included.
     * 
     * @param DataFile $dataFile the DataFile we want to convert
     * @param int $need 
     *      Every number (power of 2) specifies single kind of
     *      information. The bit is set to 1 when we want the info, 
     *      zero otherwise (usage A|B|C means we want all A, B and C fields).
     * 
     * @return VirtualFile the new virtual file instance
     */
    public static function createFromData(DataFile $dataFile, $need = self::NONE) {
        
        //Setting URL and name (root has very special URL and name, it's set in a different way)
        if ($dataFile->isRoot()) {
            $virtualFile = new VirtualFile(VirtualFile::VIRTUAL_ROOT, null);
        } else {
            $virtualFile = new VirtualFile($dataFile->getName(), VirtualFile::VIRTUAL_ROOT . $dataFile->getUrl());
        }
        
        //Setting fstorage ID
        if ($need & self::FSTORAGE) {
            $virtualFile->fstorageIdFromDataFile($dataFile);
        }
        
        //Setting mimetype
        if ($need & self::MIMETYPE) {
            $virtualFile->mimetypeIdFromDataFile($dataFile);
            
        }
                
        //Setting other file info
        $dataFileInfo = $dataFile->getFileInfo();
        if ($dataFileInfo !== null) {
            if ($need & self::BASIC_INFO) {
                $virtualFile->setHidden($dataFileInfo->getHidden());
                $virtualFile->setProtected($dataFileInfo->getProtected());
                $virtualFile->setEncrypted($dataFileInfo->getEncrypted());
            }
            
            if ($need & self::OWNER) {
                $fstorageIdRow = dibi::fetch("SELECT [id] FROM [users] WHERE [user_name]=%s", 
                        $dataFileInfo->getOwnerName());
                $virtualFile->setOwnerId($fstorageIdRow["id"]);
            }
            
            if ($need & self::LOCKEDBY) {
                $mimetypeIdRow = dibi::fetch("SELECT [id] FROM [users] WHERE [user_name]=%s", 
                        $dataFileInfo->getLockedByName());
                $virtualFile->setLockedById($mimetypeIdRow["id"]);
            }
        }
        
        return $virtualFile;
    }
    
    /**
     * Setting fstorage ID: 
     * 
     * If the fstorage name has not been specified, 
     * we try to find whether the file already exists in DB and set it
     * according to that record.
     * 
     * @param DataFile $dataFile
     */
    private function fstorageIdFromDataFile(DataFile $dataFile) {
        
        if ($dataFile->getFstorageName() === null) {
            //The fstorage name has not been assigned. Let's try to find the file in DB.
            $fstorageIdRow = dibi::select("id_files_storage")->from("files")
                    ->where("%and", array("url" => $this->getUrl(), "name_file" => $this->getName()))
                    ->fetch();
            
            //The file exists in DB, let's assign the ID and leave.
            if ($fstorageIdRow !== false) {
                $this->setFstorageId($fstorageIdRow["id_files_storage"]);
                return;
            } 

            //The file does not exist in the FS and the user hasn't specified the fstorage name.
            //Default value will be used.
            $dataFile->setFstorageName(DataFile::DEFAULT_FSTORAGE_NAME());
        }
        
        //The fstorage name is set to not-null value by now
        if ($dataFile->getFstorageName() == null) {
            throw new InvalidStateException("Fstorage name is NULL. This should never happen");
        }
        
        $fstorageIdRow = dibi::fetch("SELECT [id_files_storage] FROM [files_storages] " .
                "WHERE [name_files_storage]=%s", $dataFile->getFstorageName());
        if ($fstorageIdRow === false) {
            throw new InvalidArgumentException("Incorrect file storage type specified: " . $dataFile->getFstorageName());
        }
        
        $this->setFstorageId($fstorageIdRow["id_files_storage"]);
    }
    
    /**
     * Sets the mimetype ID according to the DataFile mimetype-name entry.
     * If the mimetype does not exist yet, it's created in the database
     * and the ID is set then.
     * 
     * @param DataFile $dataFile 
     */
    private function mimetypeIdFromDataFile(DataFile $dataFile) {
        $mimetypeName = $dataFile->getMimetypeText();
        $this->setMimetypeId($this->createMimeType($mimetypeName));
    }
    
    public static function createMimeType($mimetypeName) {
        if (empty($mimetypeName) || !is_string($mimetypeName)) {
            throw new InvalidArgumentException("Mimetype name must be non-empty string, " . gettype($mimetypeName) . " given.");
        } else {
            $mimetypeRow = dibi::fetch("SELECT [id_mimetype] FROM [mimetypes] " . 
                    "WHERE [name_mimetype]=%sN", $mimetypeName);
            if ($mimetypeRow === false) {
                dibi::query("INSERT INTO [mimetypes] ([name_mimetype]) " . 
                        "VALUES (%s)", $mimetypeName);
                $mimetypeId = dibi::getInsertId();
            } else {
                $mimetypeId = $mimetypeRow["id_mimetype"];
            }
        }
        return $mimetypeId;
    }

}
